﻿using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using System.Linq.Expressions;
using Magiq.Providers.Sql.Support;

namespace Magiq.Providers.Sql.Query
{
    public abstract class TableCompiledQueryableProvider<TExpression> where TExpression : LambdaExpression
    {
        protected static TExpression CreateLambda(ITable table, LambdaExpression expression)
        {
            var parentType = table.ElementType;
            var dcParameter = Expression.Parameter(typeof(DataContext), "dc");
            var tableExpression = Expression.Call(dcParameter, "GetTable", new[] { parentType });

            var resultExpression = new ExpressionParameterShifter(expression.Parameters[0], tableExpression).Visit(expression.Body);

            var argsParameters = new ParameterExpression[expression.Parameters.Count];
            argsParameters[0] = dcParameter;

            for (var i = 1; i < expression.Parameters.Count; i++)
                argsParameters[i] = expression.Parameters[i];

            var delegateType = typeof(TExpression).GetGenericArguments().Single();
            return (TExpression)Expression.Lambda(delegateType, resultExpression, argsParameters);
        }
    }

    public class TableCompiledQueryableProvider<T, TResult> :
        TableCompiledQueryableProvider<Expression<Func<DataContext, IQueryable<TResult>>>> where T : class
    {
        private static readonly IDictionary<string, Func<DataContext, IQueryable<TResult>>> CompiledQueries =
            new Dictionary<string, Func<DataContext, IQueryable<TResult>>>();

        public static IQueryable<TResult> GetQueryable(Table<T> table, string key,
                                                       Expression<Func<IQueryable<T>, IQueryable<TResult>>> expression)
        {
            var dc = table.DataContext();
            Func<DataContext, IQueryable<TResult>> compiledQuery;

            if (!CompiledQueries.TryGetValue(key, out compiledQuery))
            {
                var lambda = CreateLambda(table, expression);
                compiledQuery = CompiledQuery.Compile(lambda);
                CompiledQueries.Add(key, compiledQuery);
            }

            return compiledQuery(dc);
        }
    }

    public class TableCompiledQueryableProvider<T, TArg0, TResult> :
        TableCompiledQueryableProvider<Expression<Func<DataContext, TArg0, IQueryable<TResult>>>> where T : class
    {
        private static readonly IDictionary<string, Func<DataContext, TArg0, IQueryable<TResult>>>
            CompiledQueries = new Dictionary<string, Func<DataContext, TArg0, IQueryable<TResult>>>();

        public static IQueryable<TResult> GetQueryable(Table<T> table, string key,
                                                       Expression<Func<IQueryable<T>, TArg0, IQueryable<TResult>>>
                                                           expression, TArg0 arg0)
        {
            Func<DataContext, TArg0, IQueryable<TResult>> compiledQuery;

            if (!CompiledQueries.TryGetValue(key, out compiledQuery))
            {
                var lambda = CreateLambda(table, expression);
                compiledQuery = CompiledQuery.Compile(lambda);
                CompiledQueries.Add(key, compiledQuery);
            }

            var dc = table.DataContext();

            return compiledQuery(dc, arg0);
        }
    }

    public class TableCompiledQueryableProvider<T, TArg0, TArg1, TResult> :
        TableCompiledQueryableProvider<Expression<Func<DataContext, TArg0, TArg1, IQueryable<TResult>>>>
        where T : class
    {
        private static readonly IDictionary<string, Func<DataContext, TArg0, TArg1, IQueryable<TResult>>>
            CompiledQueries = new Dictionary<string, Func<DataContext, TArg0, TArg1, IQueryable<TResult>>>();

        public static IQueryable<TResult> GetQueryable(Table<T> table, string key,
                                                       Expression
                                                           <Func<IQueryable<T>, TArg0, TArg1, IQueryable<TResult>>>
                                                           expression, TArg0 arg0, TArg1 arg1)
        {
            Func<DataContext, TArg0, TArg1, IQueryable<TResult>> compiledQuery;

            if (!CompiledQueries.TryGetValue(key, out compiledQuery))
            {
                var lambda = CreateLambda(table, expression);
                compiledQuery = CompiledQuery.Compile(lambda);
                CompiledQueries.Add(key, compiledQuery);
            }

            var dc = table.DataContext();

            return compiledQuery(dc,  arg0, arg1);
        }
    }
}